#include <stdlib.h>

#include "core.h"
#include "hardware.h"
#include "hw/profiler.h"
#include "log.h"
#include "cpu/int.h"
#include "cpu/mips.h"
#include "gfx/display.h"

#include "memmap.h"

MemRegionInfo defaultRegions[] =
{
    {"Scratchpad",            MRTYPE_MEMORY, 0x00010000, 0x00020000, NULL, NULL,      NULL},
    {"Frame Buffer",          MRTYPE_MEMORY, 0x04000000, 0x04200000, NULL, NULL,      NULL},
    {"Kernel Memory",         MRTYPE_MEMORY, 0x08000000, 0x08800000, NULL, NULL,      NULL},
    {"User Memory",           MRTYPE_MEMORY, 0x08800000, 0x0a000000, NULL, NULL,      NULL},
    {"EDRam",                 MRTYPE_MEMORY, 0x0a000000, 0x0a100000, NULL, NULL,      NULL},
    {"Hardware",              MRTYPE_FUNC,   0x3c000000, 0x3c400000, NULL, hw_read,   hw_write},
    {"Hw - profiler",         MRTYPE_FUNC,   0x3c400000, 0x3c500000, NULL, prof_read, prof_write},
    {"Hardware",              MRTYPE_FUNC,   0x3c500000, 0xc0000000, NULL, hw_read,   hw_write}
};

const u32 numDefaultRegions = sizeof(defaultRegions) / sizeof(MemRegionInfo);

MemRegionInfo *regions = NULL;

void memMap_addRegion(MemRegionInfo region)
{
    u32 actualStart = region.start;
    u32 actualEnd   = region.end;

    u32 memorySize  = actualEnd - actualStart;

    u32 i;

    if (regions)
        regions = realloc(regions, sizeof(MemRegionInfo) * (cpu.memMap.numRegions + 1));
    else
        regions = malloc(sizeof(MemRegionInfo));

    switch (region.type)
    {
    case MRTYPE_MEMORY:
        region.memPointer = malloc(memorySize * sizeof(u8));
        memset(region.memPointer, 0x61, memorySize);
        break;

    default:
        region.memPointer = NULL;
        break;
    }
    
    for (i = actualStart >> MEMTABLE_SHIFT; i < actualEnd >> MEMTABLE_SHIFT; i++)
    {
        cpu.memMap.memTable[i].func_read = region.func_read;
        cpu.memMap.memTable[i].func_write = region.func_write;

        if (region.memPointer != NULL) {
            cpu.memMap.memTable[i].ptr = region.memPointer - actualStart;
            cpu.memMap.memTable[i].state = MEMSTATE_READY;
        }
        else if (region.type == MRTYPE_FUNC)
            cpu.memMap.memTable[i].state = MEMSTATE_FUNC;
    }

    regions[cpu.memMap.numRegions] = region;

    cpu.memMap.numRegions++;
}

void memMap_delRegion(u32 start)
{
    u32 i, j;
    for (i = 0; i < cpu.memMap.numRegions; i++)
    {
        if (regions[i].start == start)
        {
            for (j = start >> MEMTABLE_SHIFT; j < regions[i].end >> MEMTABLE_SHIFT; j++)
                cpu.memMap.memTable[j].state = MEMSTATE_INVALID;
            if (regions[i].type == MRTYPE_MEMORY)
                free(regions[i].memPointer);
    
            for (j = i; j < cpu.memMap.numRegions; j++)
                regions[j] = regions[j + 1];

            cpu.memMap.numRegions--;
            if (cpu.memMap.numRegions > 0)
                regions = realloc(regions, sizeof(MemRegionInfo) * cpu.memMap.numRegions);
            else
                free(regions);

            return;
        }
    }
    _log(ERR, MEMMAP, "Can't delete region: no region found at %08x!", start);
}

void memMap_init()
{
    u32 i;

    cpu.memMap.numRegions = 0;

    for (i = 0; i < MEMTABLE_SIZE; i++)
        cpu.memMap.memTable[i].state = MEMSTATE_INVALID;

    for (i = 0; i < numDefaultRegions; i++)
        memMap_addRegion(defaultRegions[i]);

    _log(INF, MEMMAP, "Memory initialized - %d regions", numDefaultRegions);
}

void memMap_free()
{
    u32 i;
    for (i = 0; i < cpu.memMap.numRegions; i++)
        if (regions[i].type == MRTYPE_MEMORY)
            free(regions[i].memPointer);
    free(regions);
}

void mem_debug(u32 address, const char *func, s32 value)
{
    switch (core_getCurThread())
    {
        case THREAD_INTERPRETER:
            cpu_printDebug();
            break;
        case THREAD_AUDIO:
            _log(ERR, MEMMAP, "From audio thread:");
            break;
        case THREAD_GRAPHICS:
            _log(ERR, MEMMAP, "From graphics thread:");
            break;
        default:
            _log(ERR, MEMMAP, "Unknown thread!! This is probably an heavy bug!");
            break;
    }
    if (value == -1)
        _log(ERR, MEMMAP, "Can't read address %x with %s!", address, func);
    else
        _log(ERR, MEMMAP, "Can't write %08x to address %x with %s!", value, address, func);
}

s8 mem_read8(u32 address)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_read)
            r.func_read(address);
        return *(s8*)(r.ptr + address);

    case MEMSTATE_FUNC:
        return r.func_read(address);

    default:
        mem_debug(address, __FUNCTION__, -1);
        return 0;
    }
}

s16 mem_read16(u32 address)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_read)
            r.func_read(address);
        return *(s16*)(r.ptr + address);

    case MEMSTATE_FUNC:
        return r.func_read(address);

    default:
        mem_debug(address, __FUNCTION__, -1);
        return 0;
    }
}

s32 mem_read32(u32 address)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_read)
            r.func_read(address);
        return *(s32*)(r.ptr + address);

    case MEMSTATE_FUNC:
        return r.func_read(address);

    default:
        mem_debug(address, __FUNCTION__, -1);
        return 0;
    }
}

s32 mem_read32_noerr(u32 address)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_read)
            r.func_read(address);
        return *(s32*)(r.ptr + address);

    case MEMSTATE_FUNC:
        return r.func_read(address);

    default:
        return 0;
    }
}

float mem_readFloat(u32 address)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_read)
            r.func_read(address);
        return *(float*)(r.ptr + address);

    case MEMSTATE_FUNC:
        return r.func_read(address);

    default:
        mem_debug(address, __FUNCTION__, -1);
        return 0;
    }
}

char *mem_readStr(u32 address)
{
    u32 sz;
    char *str;
    for (sz = 0; mem_read8(address + sz); sz++)
        ;
    str = malloc(sz + 1);
    for (sz = 0; (str[sz] = mem_read8(address + sz)); sz++)
        ;
    return str;
}

void mem_freeStr(char *str)
{
    free(str);
}

void mem_write8(u32 address, u8 value)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_write)
            r.func_write(address, value);
        *(u8*)(r.ptr + address) = value;
        break;

    case MEMSTATE_FUNC:
        r.func_write(address, value);
        break;

    default:
        mem_debug(address, __FUNCTION__, value);
        break;
    }
}

void mem_write16(u32 address, s16 value)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_write)
            r.func_write(address, value);
        *(s16*)(r.ptr + address) = value;
        break;

    case MEMSTATE_FUNC:
        r.func_write(address, value);
        break;

    default:
        mem_debug(address, __FUNCTION__, value);
        break;
    }
}

void mem_write32(u32 address, s32 value)
{
    MemRegion r;
    address &= ~0xc0000000;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_write)
            r.func_write(address, value);
        *(s32*)(r.ptr + address) = value;
        break;

    case MEMSTATE_FUNC:
        r.func_write(address, value);
        break;

    default:
        mem_debug(address, __FUNCTION__, value);
        break;
    }
}

void mem_writeFloat(u32 address, float value)
{
    MemRegion r;
    address &= ~0xc0000003;
    r = cpu.memMap.memTable[address >> MEMTABLE_SHIFT];

    switch (r.state)
    {
    case MEMSTATE_READY:
        if (r.func_write)
            r.func_write(address, value);
        *(float*)(r.ptr + address) = value;
        break;

    case MEMSTATE_FUNC:
        r.func_write(address, value);
        break;

    default:
        mem_debug(address, __FUNCTION__, value);
        break;
    }
}

void mem_readStruct(u32 address, void *ptr, u32 size)
{
    u32 i;

    for (i = 0; i < size; i++)
        *((s8*)ptr + i) = mem_read8(address + i);
}

void mem_writeStruct(u32 address, void *ptr, u32 size)
{
    u32 i;

    for (i = 0; i < size; i++)
        mem_write8(address + i, *((s8*)ptr + i));
}

u8 mem_isOk(u32 address)
{
    address &= ~0xc0000000;
    return (cpu.memMap.memTable[address >> MEMTABLE_SHIFT].state == MEMSTATE_READY);
}

